<template>
  <el-container class="chat-layout">
    <messages-conversation />
    <el-main class="chat-main">
      <div class="chat-container">
        <div class="chat-messages" ref="messagesContainer">
          
        <!-- 空状态 -->
        <div v-if="messages.length === 0" class="empty-chat">
          <div class="empty-icon">💬</div>
          <h2>开始新的对话</h2>
          <p>在下方输入框中输入您的问题，我将为您解答</p>
        </div>
        
        <!-- 消息列表 -->
        <div v-else>
          <div 
            v-for="(message, index) in messages" 
            :key="index"
            :class="['message', message.sender_type === 0 ? 'user-message' : 'ai-message']"
          >
            <div class="message-avatar">
              {{ message.sender_type === 0 ? 'U' : 'AI' }}
            </div>
            <div class="message-content">
              <div v-if="message.sender_type === 1 && message.isStreaming">
                <div v-html="formatMessage(message.content)"></div>
                <span class="typing-indicator">▋</span>
              </div>
              <div v-else v-html="formatMessage(message.content)"></div>
              <div class="message-time">{{ formatTime(message.send_time) }}</div>
              <div v-if="message.sender_type===1 && message.citations && message.citations.length" class="citations">
                <div class="c-title">引用来源（{{ message.citations.length }}）</div>
                <div class="c-list">
                  <div v-for="(hit, i) in message.citations" :key="i" class="c-item" @click="goToDoc(hit)">
                    <span class="c-doc">{{ hit.doc_name || ('文档#' + hit.doc_id) }}</span>
                    <span class="c-sep">·</span>
                    <span class="c-score">{{ (hit.score*100).toFixed(1) }}%</span>
                    <span class="c-text">{{ hit.chunk_text }}</span>
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
        </div>
      </div>
      
      <div class="chat-input-container">
        <div class="input-status" v-if="isTyping">
          <el-icon class="typing-spinner"><Loading /></el-icon>
          <span>AI正在思考中...</span>
        </div>
        
        <div class="chat-input-wrapper">
          <div class="input-field-group">
            <div class="input-container">
              <!-- 模型选择下拉框 - 嵌入输入框内部左上角 -->
              <div class="model-select-container">
                <div class="selectors">
                  <el-popover
                    v-model:visible="settingsVisible"
                    placement="top-start"
                    :width="520"
                    trigger="click"
                    :show-arrow="false"
                    popper-class="settings-popper"
                  >
                    <template #reference>
                      <el-button class="settings-toggle" type="primary" size="small">
                        <el-icon><Setting /></el-icon>
                        <span>对话设置</span>
                      </el-button>
                    </template>
                    <div class="settings-panel">
                      <div class="settings-title">对话设置</div>
                      <el-form label-width="88px" class="settings-form">
                        <el-form-item label="模型">
                          <el-select v-model="selectedModel" placeholder="选择模型" :disabled="isTyping" filterable style="width:100%" :teleported="false">
                            <el-option v-for="model in models" :key="model.model_id" :label="model.display_name" :value="model.model_name" />
                          </el-select>
                        </el-form-item>
                        <el-form-item label="提示词模板">
                          <el-select v-model="selectedPromptId" placeholder="选择提示词" :disabled="isTyping || !!currentConversationId || messages.length>0" filterable style="width:100%" :teleported="false">
                            <el-option v-for="p in promptOptions" :key="p.system_prompt_id" :label="(p.title && p.title.trim()) || snippet(p.content) || ('提示词#' + p.system_prompt_id)" :value="p.system_prompt_id" />
                          </el-select>
                        </el-form-item>
                        <template v-if="selectedPromptHasContext">
                          <el-form-item label="知识库">
                            <el-select v-model="selectedKbId" placeholder="选择知识库" :disabled="isTyping || !!currentConversationId || messages.length>0" filterable style="width:100%" :teleported="false">
                              <el-option v-for="kb in kbOptions" :key="kb.id" :label="kb.name" :value="kb.id" />
                            </el-select>
                          </el-form-item>
                          <el-form-item label="TopK">
                            <el-input-number v-model="recallTopK" :min="1" :max="20" :step="1" controls-position="right" />
                          </el-form-item>
                          <el-form-item label="相似度阈值">
                            <el-input-number v-model="recallThreshold" :min="0" :max="1" :step="0.05" controls-position="right" />
                          </el-form-item>
                        </template>
                      </el-form>
                    </div>
                  </el-popover>
                </div>
              </div>
              
              <el-input
                v-model="inputMessage"
                type="textarea"
                :rows="1"
                placeholder="输入消息..."
                class="chat-input"
                resize="none"
                @keyup.enter.exact="sendMessage"
                :disabled="isTyping"
              />
            </div>
            <div class="input-hint">
              <span v-if="currentPromptDisplayName" class="current-prompt">
                当前系统提示词：{{ currentPromptDisplayName }}
              </span>
              <span class="hint-text">按 Enter 发送消息，Shift+Enter 换行</span>
              <span v-if="selectedModel" class="current-model">
                当前模型：{{ getSelectedModelDisplayName() }}
              </span>
            </div>
          </div>
          <el-button 
            :type="isTyping ? 'danger' : 'primary'" 
            class="send-btn"
            :class="{ 'is-stop': isTyping }"
            @click="isTyping ? stopGeneration() : sendMessage()"
            :disabled="!isTyping && (!inputMessage.trim() || !selectedModel)"
          >
            <template v-if="isTyping">
              <span>停止</span>
            </template>
            <template v-else>
              发送
            </template>
          </el-button>
        </div>
        
        
      </div>
    </el-main>
  </el-container>

</template>

<script>
import { apiService } from '@/services/api'
import { API_BASE } from '@/config'
import { ElMessage } from 'element-plus'
import { Loading, Setting } from '@element-plus/icons-vue'
import { renderMarkdown } from '@/utils/markdown'
import { emit } from '@/utils/eventBus'
import MessagesConversation from '@/components/messages_conversation.vue'

export default {
  name: 'HomeIndex',
  components: {
    Loading,
    Setting,
    MessagesConversation
  },
  data() {
    return {
      inputMessage: '',
      messages: [],
      isTyping: false,
      currentConversationId: null,
      abortController: null,
      models: [], // 新增：用于存储模型列表
      selectedModel: '', // 新增：当前选择的模型
      pendingConversationId: null,
      promptOptions: [],
      selectedPromptId: null,
      kbOptions: [],
      selectedKbId: null,
      recallTopK: 6,
      recallThreshold: 0,
      settingsVisible: false
    }
  },
  mounted() {
    this.loadConversations()
    this.loadModels() // 新增：加载模型列表
    this.loadPrompts()
    this.loadKnowledgeBases()
    const id = this.$route.query.conversation_id
    if (id) {
      this.onSelectConversation({ conversation_id: Number(id) })
    }
  },
  computed: {
    currentPromptDisplayName(){
      return this.getSelectedPromptDisplayNameById(this.selectedPromptId)
    },
    selectedPromptHasContext(){
      const id = this.selectedPromptId
      if (!id) return false
      const p = (this.promptOptions || []).find(x => x.system_prompt_id === id)
      return !!(p && p.enable_context)
    }
  },
  watch: {
    '$route.query.conversation_id'(newId, oldId) {
      if (this.isTyping) return
      if (newId && newId !== oldId) {
        this.onSelectConversation({ conversation_id: Number(newId) })
      }
      if (!newId) {
        this.onCreateNewChat()
        // 新建会话时允许重新选择提示词
        this.selectedPromptId = null
      }
    },
    selectedPromptId(newVal, oldVal){
      // 只有在新建会话且主动切换提示词时才清空已选知识库
      // 加载已有会话时不应该清空知识库选择
      if (oldVal !== null && newVal !== oldVal && this.currentConversationId === null) {
        this.selectedKbId = null
      }
    }
  },
  methods: {
    async loadConversations() {
      try {
        const response = await apiService.getConversations()
        if (response.conversations && response.conversations.length > 0) {
          console.log('找到会话列表:', response.conversations)
        }
      } catch (error) {
        console.error('加载会话列表失败:', error)
      }
    },
    async onSelectConversation(conv) {
      try {
        this.currentConversationId = conv.conversation_id
        // 加载会话概要信息以获取 system_prompt_id 和 kb_id
        try {
          const info = await apiService.getConversation(this.currentConversationId)
          if (info) {
            if (typeof info.system_prompt_id !== 'undefined') {
              this.selectedPromptId = info.system_prompt_id || null
            }
            // 恢复会话的知识库选择
            if (typeof info.kb_id !== 'undefined') {
              this.selectedKbId = info.kb_id || null
            }
          }
        } catch (e) { /* 忽略 */ }
        const res = await apiService.getConversationMessages(this.currentConversationId)
        this.messages = Array.isArray(res && res.messages) ? res.messages : []
        this.$nextTick(() => {
          this.scrollToBottom()
        })
      } catch (e) {
        console.error('加载会话消息失败:', e)
        ElMessage.error('加载会话消息失败')
      }
    },
    onCreateNewChat() {
      this.currentConversationId = null
      this.messages = []
    },
    
    async sendMessage() {
      if (!this.inputMessage.trim() || this.isTyping) return

      // 检查是否选择了模型
      if (!this.selectedModel) {
        ElMessage.warning('请先选择一个AI模型')
        return
      }

      const message = this.inputMessage.trim()
      this.inputMessage = ''

      const userMessage = {
        sender_type: 0,
        content: message,
        send_time: new Date()
      }
      this.messages.push(userMessage)

      const aiMessage = {
        sender_type: 1,
        content: '',
        send_time: new Date(),
        isStreaming: true
      }
      this.messages.push(aiMessage)

      this.isTyping = true
      this.$nextTick(() => {
        this.scrollToBottom()
      })

      try {
        const payload = { question: message }
        if (this.currentConversationId !== null && this.currentConversationId !== undefined) {
          payload.conversation_id = this.currentConversationId
        }
        // 添加选择的模型
        if (this.selectedModel) {
          payload.model_name = this.selectedModel
        }
        // 新建会话时附上系统提示词ID（仅一次）
        if (!this.currentConversationId && this.selectedPromptId) {
          payload.system_prompt_id = this.selectedPromptId
        }
        // 若启用上下文并选择了知识库，每次提问都召回（多轮生效）
        if (this.selectedPromptHasContext && this.selectedKbId) {
          payload.kb_id = this.selectedKbId
          payload.top_k = Number(this.recallTopK) || 6
          payload.threshold = Number(this.recallThreshold) || 0
          payload.include_citations = true
        }

        const aiIndex = this.messages.length - 1
        await this.startFetchStream(payload, aiIndex)

      } catch (error) {
        if (error.name === 'AbortError') {
          console.log('请求被取消')
        } else {
          console.error('发送消息失败:', error)
          ElMessage.error('发送消息失败，请稍后重试')
          this.messages.pop()
        }
        this.isTyping = false
      }
    },

    async startFetchStream(payload, aiIndex) {
        this.abortController = new AbortController()
        
        // 获取认证头
        const getAuthHeaders = () => {
          const headers = {
            'Content-Type': 'application/json',
            'Accept': 'text/event-stream',
            'Cache-Control': 'no-cache',
            'Pragma': 'no-cache'
          }
          
          // 添加认证token（从localStorage获取）
          const token = localStorage.getItem('token')
          if (token) {
            headers.Authorization = `Bearer ${token}`
          }
          
          return headers
        }
        
      // 优先直连后端，再回退代理
      let response
      try {
        const directUrl = `${API_BASE}/stream-question`
        console.log('[stream] try direct POST:', directUrl, payload)
        response = await fetch(directUrl, {
          method: 'POST',
          headers: getAuthHeaders(),
          cache: 'no-cache',
          body: JSON.stringify(payload),
          signal: this.abortController.signal
        })
      } catch (err) {
        const proxyUrl = `/api/stream-question`
        console.warn('[stream] direct failed, fallback to proxy POST:', proxyUrl, err)
        response = await fetch(proxyUrl, {
          method: 'POST',
          headers: getAuthHeaders(),
          cache: 'no-cache',
          body: JSON.stringify(payload),
          signal: this.abortController.signal
        })
      }

      console.log('[stream] response.ok:', response.ok, 'status:', response.status)
      console.log('[stream] response.headers:', Array.from(response.headers.entries()))

        if (!response.ok) {
          throw new Error(`HTTP error! status: ${response.status}`)
        }

        const reader = response.body.getReader()
        const decoder = new TextDecoder()
        let buffer = ''
        let reading = true

      console.log('[stream] start reading...')

        while (reading) {
          const { done, value } = await reader.read()
          if (done) {
          console.log('[stream] reader done=true, exit loop')
            reading = false
            break
          }

        const decoded = decoder.decode(value, { stream: true })
        console.log('[stream] decoded chunk:', decoded)
        buffer += decoded
          const lines = buffer.split('\n')
          buffer = lines.pop() || ''

          for (const line of lines) {
          console.debug('[stream] raw line:', line)
            if (line.trim() === '') continue
            if (line.startsWith('data: ')) {
              const data = line.slice(6)
            console.debug('[stream] data payload:', data)
              if (data === '[DONE]') {
              console.log('[stream] got [DONE]')
              const aiMsg = this.messages[aiIndex]
              if (aiMsg) {
                aiMsg.isStreaming = false
                if (Array.isArray(aiMsg._pendingCitations)) {
                  aiMsg.citations = aiMsg._pendingCitations
                  delete aiMsg._pendingCitations
                }
              }
                this.isTyping = false
              this.$nextTick(() => this.scrollToBottom())
              // 流结束后同步路由，避免中断流
              if (this.pendingConversationId) {
                this.$router.replace({ name: 'home', query: { conversation_id: this.pendingConversationId } })
                this.pendingConversationId = null
              }
                return
              }
              try {
                const parsed = JSON.parse(data)
              console.debug('[stream] parsed object:', parsed)
                // 首帧可能携带 recall 引用：先暂存，待本条AI输出完成后再展示
                if (parsed.recall && Array.isArray(parsed.recall.hits)) {
                  const aiMsg = this.messages[aiIndex]
                  if (aiMsg) aiMsg._pendingCitations = parsed.recall.hits
                }
                if (parsed.content) {
                const aiMsg = this.messages[aiIndex]
                if (aiMsg) aiMsg.content = (aiMsg.content || '') + parsed.content
                this.$nextTick(() => this.scrollToBottom())
                }
                if (parsed.conversation_id && !this.currentConversationId) {
                  this.currentConversationId = parsed.conversation_id
                  // 通知侧边栏刷新
                  emit('conversation:created', { conversation_id: this.currentConversationId })
                  // 延迟同步路由到流结束，避免中途刷新消息列表导致流中断
                  this.pendingConversationId = this.currentConversationId
                }
              if (parsed.error) {
                throw new Error(parsed.error)
              }
              } catch (e) {
              console.error('[stream] JSON parse failed:', e)
              }
            }
          }
        }

      const aiMsg = this.messages[aiIndex]
      if (aiMsg) aiMsg.isStreaming = false
        this.isTyping = false
      this.$nextTick(() => this.scrollToBottom())
      // 流结束后再同步路由，避免中断流
      if (this.pendingConversationId) {
        this.$router.replace({ name: 'home', query: { conversation_id: this.pendingConversationId } })
        this.pendingConversationId = null
      }
          },

    async stopGeneration() {
      try {
        if (this.abortController) {
          this.abortController.abort()
        }
        // 后端取消（若有会话ID）
        const convId = this.currentConversationId || this.pendingConversationId
        if (convId) {
          try { await apiService.cancelGeneration(convId) } catch (e) { console.warn('取消生成接口调用失败', e) }
        }
        // 将流式消息置为完成态，移除光标动画
        for (let i = this.messages.length - 1; i >= 0; i--) {
          const msg = this.messages[i]
          if (msg && msg.sender_type === 1 && msg.isStreaming) {
            msg.isStreaming = false
            break
          }
        }
        // 若已拿到新会话ID但未完成流，仍然同步路由，避免丢失定位
        if (this.pendingConversationId) {
          this.$router.replace({ name: 'home', query: { conversation_id: this.pendingConversationId } })
          this.pendingConversationId = null
        }
      } catch (e) {
        console.error('停止生成失败:', e)
      } finally {
        this.isTyping = false
        this.abortController = null
      }
    },

      async loadModels() {
      try {
        const response = await apiService.getModels('chat')
        if (response.models && response.models.length > 0) {
          this.models = response.models
          // 设置默认选择第一个模型
          if (!this.selectedModel && this.models.length > 0) {
            this.selectedModel = this.models[0].model_name
          }
          console.log('找到模型列表:', this.models)
        }
      } catch (error) {
        console.error('加载模型列表失败:', error)
      }
    },

    async loadPrompts(){
      try{
        const res = await apiService.listSystemPrompts(1, 100)
        this.promptOptions = res.prompts || []
      }catch(e){ /* ignore */ }
    },

    async loadKnowledgeBases(){
      try{
        const res = await apiService.listKnowledgeBases(1, 100)
        // 兼容返回结构 { items } 或 { knowledge_bases }
        this.kbOptions = (res.items || res.knowledge_bases || res.list || []).map(x => ({ id: x.id, name: x.name }))
      }catch(e){ /* ignore */ }
    },

    getSelectedModelDisplayName() {
      const model = this.models.find(m => m.model_name === this.selectedModel)
      return model ? model.display_name : this.selectedModel
    },

    getSelectedPromptDisplayNameById(id){
      if (!id) return ''
      const p = (this.promptOptions || []).find(x => x.system_prompt_id === id)
      if (!p) return ''
      return (p.title && p.title.trim()) || this.snippet(p.content) || ('提示词#' + p.system_prompt_id)
    },

    snippet(text){
      const t = (text || '').trim().replace(/\s+/g, ' ')
      return t.length > 24 ? t.slice(0, 24) + '…' : t
    },

    formatMessage(content) {
      if (!content) return ''
      return renderMarkdown(content)
    },

    formatTime(date) {
      if (!date) return ''
      const d = new Date(date)
      return d.toLocaleTimeString('zh-CN', { 
        hour: '2-digit', 
        minute: '2-digit' 
      })
    },

    scrollToBottom() {
      this.$nextTick(() => {
        const container = this.$refs.messagesContainer
        if (container) {
          container.scrollTop = container.scrollHeight
        }
        // 增强代码块：添加复制按钮
        this.enhanceCodeBlocks()
      })
    },

    enhanceCodeBlocks() {
      const container = this.$refs.messagesContainer
      if (!container) return
      const pres = container.querySelectorAll('.message-content pre')
      pres.forEach((pre) => {
        if (!pre.classList.contains('has-copy-btn')) {
          pre.classList.add('has-copy-btn')
        }
        if (pre.querySelector('.copy-code-btn')) {
          return
        }
        const btn = document.createElement('button')
        btn.className = 'copy-code-btn'
        btn.type = 'button'
        btn.textContent = '复制'
        btn.addEventListener('click', async (e) => {
          e.stopPropagation()
          try {
            const code = pre.querySelector('code')
            const text = code ? code.textContent || '' : pre.textContent || ''
            if (navigator.clipboard && navigator.clipboard.writeText) {
              await navigator.clipboard.writeText(text)
            } else {
              const textarea = document.createElement('textarea')
              textarea.value = text
              textarea.style.position = 'fixed'
              textarea.style.opacity = '0'
              document.body.appendChild(textarea)
              textarea.select()
              document.execCommand('copy')
              document.body.removeChild(textarea)
            }
            const old = btn.textContent
            btn.textContent = '已复制'
            setTimeout(() => { btn.textContent = old }, 1200)
          } catch (err) {
            const old = btn.textContent
            btn.textContent = '失败'
            setTimeout(() => { btn.textContent = old }, 1200)
          }
        })
        pre.appendChild(btn)
      })
    },
    goToDoc(hit){
      if (!hit) return
      const docId = hit.doc_id
      if (!docId) return
      // 跳转到文档分块页
      this.$router.push({ name: 'document-chunks', params: { docId } })
    },
  }
}
</script>

<style scoped>
.chat-layout {
  height: calc(100vh - var(--header-height)) !important;
  max-height: calc(100vh - var(--header-height)) !important;
  overflow: hidden !important;
  box-sizing: border-box;
  width: 100%;
  margin: 0;
  padding: 0;
}
.chat-main {
  height: calc(100vh - var(--header-height)) !important;
  max-height: calc(100vh - var(--header-height)) !important;
  padding: 24px 24px 24px 8px;
  background-color: var(--bg);
  display: flex;
  flex-direction: column;
  overflow: hidden !important;
  box-sizing: border-box;
  min-height: 0;
  width: 100%;
}

.chat-container {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
  overflow: hidden;
}

.chat-messages {
  flex: 1;
  overflow-y: auto;
  overflow-x: hidden;
  padding: 16px;
  display: flex;
  flex-direction: column;
  gap: 24px;
  min-height: 0;
}

.message {
  display: flex;
  gap: 12px;
  max-width: 900px;
  margin: 0 auto;
  width: 100%;
}

.user-message {
  flex-direction: row-reverse;
}

.message-avatar {
  width: 36px;
  height: 36px;
  border-radius: 12px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 14px;
  font-weight: 700;
  flex-shrink: 0;
  background: linear-gradient(135deg, var(--accent), var(--accent-2));
  color: #fff;
  box-shadow: 0 8px 20px rgba(61,85,236,.25);
}

.user-message .message-avatar {
  background: linear-gradient(135deg, #1e293b, #334155);
}

.ai-message .message-avatar {
  background-color: #968c8c;
  color: rgb(0, 0, 0);
}

.message-content {
  background-color: var(--card-bg);
  padding: 10px 16px;
  border-radius: 14px;
  flex: 1;
  min-width: 0;
  overflow-x: hidden;
  color: #000000;
  line-height: 1.8;
  border: 1px solid var(--border);
  box-shadow: var(--shadow-sm);
  margin: 5px;
}

.user-message .message-content {
  background: #ffffff;
  border: 1px solid var(--border);
}

.message-content p {
  margin: 0 0 8px 0;
}

.message-content p:last-child {
  margin-bottom: 0;
}

.message-content strong {
  color: #000000;
  font-weight: 600;
}

.chat-input-container {
  padding: 24px 16px 4px;
  background-color: transparent;
  flex-shrink: 0;
}

.chat-input-wrapper {
  max-width: 900px;
  margin: 0 auto;
  display: flex;
  gap: 12px;
  align-items: flex-end;
}

.input-field-group {
  flex: 1;
  display: flex;
  flex-direction: column;
}

.input-container {
  position: relative;
  background-color: #ffffff;
  border: 1px solid var(--border);
  border-radius: 16px;
  transition: box-shadow .2s ease, border-color .2s ease;
  overflow: hidden;
}

.input-container:focus-within {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(61,85,236,.12);
}

.model-select-container {
  position: absolute;
  top: 8px;
  left: 8px;
  z-index: 10;
}
.selectors{ display:flex; gap: 8px; align-items: center; }
.settings-toggle{ border-radius: 12px; background: linear-gradient(135deg, var(--accent), var(--accent-2)); border: none; box-shadow: 0 6px 16px rgba(61,85,236,.2); }

/* 响应式设计 */
@media (max-width: 768px) {
  .model-select {
    width: 110px;
    font-size: 10px;
  }
  
  .model-select :deep(.el-input__wrapper) {
    height: 26px;
    border-radius: 12px;
  }
  
  .model-select :deep(.el-input__inner) {
    font-size: 10px;
    padding: 0 6px;
  }
  
  .model-select-container {
    top: 6px;
    left: 6px;
  }
  
  .chat-input :deep(.el-textarea__inner) {
    padding: 36px 12px 12px 12px;
    min-height: 56px;
    font-size: 13px;
  }
  
  .input-container {
    border-radius: 14px;
  }
  
  .chat-input-container {
    padding: 20px 12px 4px;
  }
}

.input-field-group .input-hint {
  position: relative;
  display: block;
  margin-top: 6px;
  padding: 0 4px;
  font-size: 12px;
  color: var(--muted);
  min-height: 18px;
}

.input-field-group .input-hint .hint-text {
  display: block;
  width: 100%;
  text-align: center;
}

.input-field-group .input-hint .current-model {
  position: absolute;
  right: 4px;
  top: 0;
}
.input-field-group .input-hint .current-prompt{
  position: absolute;
  left: 4px;
  top: 0;
  color: var(--accent);
}

.model-select {
  width: 130px;
  font-size: 11px;
}
.prompt-select{ width: 200px; font-size: 11px; }
.kb-select{ width: 180px; font-size: 11px; }
.topk-input{ width: 120px; }
.threshold-input{ width: 140px; }

.model-select :deep(.el-input__wrapper) {
  background: linear-gradient(135deg, #f8fafc, #e2e8f0);
  border: none;
  border-radius: 14px;
  height: 28px;
  transition: all .2s ease;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
}

.model-select :deep(.el-input__wrapper):hover {
  background: linear-gradient(135deg, #f1f5f9, #e2e8f0);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
  transform: translateY(-1px);
}

.model-select :deep(.el-input.is-focus .el-input__wrapper),
.model-select :deep(.el-input__wrapper:focus-within) {
  background: linear-gradient(135deg, #e2e8f0, #cbd5e1);
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.15);
}

.model-select :deep(.el-input__inner) {
  color: #475569;
  font-size: 11px;
  font-weight: 500;
  padding: 0 8px;
}

.model-select :deep(.el-input__inner::placeholder) {
  color: #94a3b8;
  font-size: 11px;
}

.model-select :deep(.el-input__suffix .el-select__caret) {
  color: #64748b;
}

/* 下拉弹层美化（使用 popper-class） */
:deep(.model-select-popper) {
  border: none;
  border-radius: 12px;
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12);
  overflow: hidden;
  padding: 6px 0;
  background: #ffffff;
}

:deep(.model-select-popper .el-select-dropdown__item) {
  padding: 10px 14px;
  line-height: 1.3;
  font-size: 12px;
  transition: all .2s ease;
  border-radius: 0;
  margin: 0 6px;
  border-radius: 8px;
}

:deep(.model-select-popper .el-select-dropdown__item.hover) {
  background: #f1f5f9;
  color: #475569;
  transform: translateX(2px);
}

:deep(.model-select-popper .el-select-dropdown__item.is-selected) {
  background: linear-gradient(135deg, #e2e8f0, #cbd5e1);
  color: #334155;
  font-weight: 600;
}

:deep(.model-select-popper .el-popper__arrow) {
  display: none;
}

/* 选项内容样式（确保作用到弹层内） */
:deep(.model-option) {
  display: flex;
  flex-direction: column;
}

:deep(.model-option .model-name) {
  font-weight: 600;
  font-size: 12px;
  color: #1e293b;
}

:deep(.model-option .model-desc) {
  font-size: 11px;
  color: #64748b;
  margin-top: 2px;
}

.chat-input {
  flex: 1;
  background-color: transparent;
  border: none;
  color: var(--text);
}

.chat-input :deep(.el-textarea__inner) {
  background-color: transparent;
  border: none;
  color: var(--text);
  resize: none;
  padding: 44px 16px 16px 16px; /* 顶部留出空间给模型+提示词选择器 */
  font-size: 14px;
  line-height: 1.6;
  min-height: 64px; /* 增加高度以容纳模型选择器 */
  max-height: 200px;
  overflow-y: auto;
  box-shadow: none;
}

.chat-input :deep(.el-textarea__inner):focus {
  box-shadow: none;
  outline: none;
}

.current-model {
  color: var(--accent);
  font-weight: 500;
}

.send-btn {
  background: linear-gradient(135deg, var(--accent), var(--accent-2));
  border: none;
  color: white;
  padding: 0 18px;
  border-radius: 16px;
  font-weight: 700;
  transition: transform .15s ease, filter .15s ease;
  height: 64px; /* 匹配新的输入框高度 */
  display: inline-flex;
  align-items: center;
  box-shadow: 0 8px 20px rgba(61,85,236,.18);
  margin-bottom: 30px;
}

.send-btn.is-stop {
  background: linear-gradient(135deg, #ef4444, #f87171);
  box-shadow: 0 8px 20px rgba(239,68,68,.18);
}

.send-btn .btn-icon { margin-right: 6px; }

.send-btn:hover {
  transform: translateY(-1px);
  filter: brightness(1.03);
}

.input-status {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  margin-bottom: 12px;
  color: var(--accent);
  font-size: 14px;
}

.typing-spinner {
  animation: spin 1s linear infinite;
}

@keyframes spin {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}

.message-time {
  font-size: 12px;
  color: var(--muted);
  margin-top: 6px;
}

.typing-indicator {
  animation: blink 1s infinite;
  color: var(--accent);
}

@keyframes blink {
  0%, 50% { opacity: 1; }
  51%, 100% { opacity: 0; }
}

/* 滚动条样式 */
.chat-messages::-webkit-scrollbar {
  width: 8px;
}

.chat-messages::-webkit-scrollbar-track {
  background: transparent;
}

.chat-messages::-webkit-scrollbar-thumb {
  background: linear-gradient(180deg, var(--accent), var(--accent-2));
  border-radius: 4px;
}

.chat-messages::-webkit-scrollbar-thumb:hover {
  filter: brightness(1.05);
}

/* 空状态样式 */
.empty-chat {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  height: 100%;
  text-align: center;
  color: #000000;
}

.empty-icon {
  font-size: 64px;
  margin-bottom: 16px;
  opacity: 0.7;
  background: linear-gradient(135deg, var(--accent), var(--accent-2));
  background-clip: text;
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
}

.empty-chat h2 {
  font-size: 22px;
  font-weight: 700;
  margin-bottom: 8px;
  color: var(--text);
}

.empty-chat p {
  font-size: 14px;
  margin: 0;
  max-width: 440px;
  line-height: 1.6;
  color: var(--muted);
}

/* 保持原有样式，下方增加对 markdown 的细化展示 */
.message-content :deep(pre) {
  background: #0b1120;
  color: #eaeef3;
  padding: 12px 14px;
  border-radius: 12px;
  overflow: auto;
  border: 1px solid var(--border);
  line-height: 1.7;
  font-size: 13.5px;
}

.message-content :deep(code) {
  background: #f3f4f6;
  color: #111827;
  padding: 2px 6px;
  border-radius: 6px;
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
}

.message-content :deep(pre code) {
  background: transparent;
  color: #eaeef3;
  padding: 0;
}

/* 代码块复制按钮样式 */
.message-content :deep(pre.has-copy-btn) {
  position: relative;
}

.message-content :deep(.copy-code-btn) {
  position: absolute;
  top: 8px;
  right: 8px;
  background: rgba(255, 255, 255, 0.08);
  border: 1px solid rgba(255, 255, 255, 0.18);
  color: #eaeef3;
  border-radius: 8px;
  padding: 4px 8px;
  font-size: 12px;
  line-height: 1;
  cursor: pointer;
  opacity: 0;
  transition: opacity .15s ease, transform .15s ease, background .2s ease;
}

.message-content :deep(pre.has-copy-btn:hover .copy-code-btn) {
  opacity: 1;
}

.message-content :deep(.copy-code-btn:hover) {
  transform: translateY(-1px);
  background: rgba(255, 255, 255, 0.16);
}

.message-content :deep(a) {
  color: var(--accent);
  text-decoration: none;
}
.message-content :deep(a:hover) {
  text-decoration: underline;
}

.message-content :deep(ul),
.message-content :deep(ol) {
  padding-left: 1.2em;
}
.settings-grid{ padding: 8px 16px 16px; }
.settings-form :deep(.el-form-item){ margin-bottom: 14px; }
.settings-popper{ border: 1px solid var(--border); box-shadow: var(--shadow-sm); border-radius: 14px; padding: 10px 12px; background: #fff; }
.settings-panel{ display: flex; flex-direction: column; gap: 8px; width: 496px; }
.settings-title{ font-weight: 700; font-size: 14px; margin-bottom: 4px; }
.settings-form :deep(.el-form-item){ margin-bottom: 12px; }
.citations{ margin-top: 8px; border-top: 1px dashed var(--border); padding-top: 6px; overflow-x: hidden; }
.citations .c-title{ font-size: 12px; color: var(--muted); margin-bottom: 4px; }
.citations .c-list{ display: flex; flex-direction: column; gap: 6px; max-width: 100%; overflow-x: hidden; }
.citations .c-item{ display:flex; align-items:center; gap: 6px; border: 1px solid var(--border); border-radius: 8px; padding: 4px 8px; cursor: pointer; transition: background .15s ease, transform .15s ease; font-size: 12px; width: 100%; max-width: 100%; overflow: hidden; white-space: nowrap; box-sizing: border-box; }
.citations .c-item:hover{ background: rgba(61,85,236,.04); transform: translateY(-1px); }
.citations .c-doc{ font-weight: 700; color: #1e293b; max-width: 28%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.citations .c-sep{ color: var(--muted); }
.citations .c-score{ color: #3d55ec; font-weight: 600; }
.citations .c-text{ flex: 1; color: #111827; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
</style>